﻿using System;
using System.ComponentModel;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;

namespace Notoric.Model.Elements.CWNotation
{
    /// <summary>
    /// Presents a <see cref="DynamicsProgressElement"/> in the Common Western Music Notation.
    /// </summary>
    public class DynamicsProgressMark : PresentedAttachment
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicsProgressMark"/> class.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The staff that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="presentedElement">
        /// <para>
        /// Type: <see cref="DynamicsProgressElement"/>
        /// </para>
        /// <para>
        /// The presented element.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal DynamicsProgressMark(XmlPackagePart packagePart, Staff owner, DynamicsProgressElement presentedElement)
            : base(packagePart, owner, presentedElement)
        {
            hairpinEnd1.SetOwner(hairpinEnd1Element);
            hairpinEnd2.SetOwner(hairpinEnd2Element);
            hairpinPoint.SetOwner(hairpinPointElement);
            Visualization = DynamicsProgressDisplayMode.Text;
            Abbreviated = false;

            presentedElement.PropertyChanged += new PropertyChangedEventHandler(PresentedElement_PropertyChanged);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicsProgressMark"/> class.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Staff"/>
        /// </para>
        /// <para>
        /// The staff that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal DynamicsProgressMark(XmlPackagePart packagePart, Staff owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            hairpinEnd1Element = element[PackageVocabulary.HairpinEnd1ElementName, PackageVocabulary.PackageNamespaceUri];
            hairpinEnd2Element = element[PackageVocabulary.HairpinEnd2ElementName, PackageVocabulary.PackageNamespaceUri];
            hairpinPointElement = element[PackageVocabulary.HairpinPointElementName, PackageVocabulary.PackageNamespaceUri];

            hairpinEnd1 = new Point(hairpinEnd1Element.FirstChild as XmlElement);
            hairpinEnd2 = new Point(hairpinEnd2Element.FirstChild as XmlElement);
            hairpinPoint = new Point(hairpinPointElement.FirstChild as XmlElement);

            visualizationAttribute = element.Attributes[PackageVocabulary.VisualizationAttribute];
            abbreviatedAttribute = element.Attributes[PackageVocabulary.AbbreviatedAttribute];

            PresentedElement.PropertyChanged += new PropertyChangedEventHandler(PresentedElement_PropertyChanged);
        }

        #endregion

        #region PresentedAttachment Members

        /// <inheritdoc/>
        protected override XmlElement CreateSymbolElement(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.DynamicsProgressMarkElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            hairpinEnd1Element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.HairpinEnd1ElementName,
                PackageVocabulary.PackageNamespaceUri
                );
            hairpinEnd2Element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.HairpinEnd2ElementName,
                PackageVocabulary.PackageNamespaceUri
                );
            hairpinPointElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.HairpinPointElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            visualizationAttribute = document.CreateAttribute(PackageVocabulary.VisualizationAttribute);
            abbreviatedAttribute = document.CreateAttribute(PackageVocabulary.AbbreviatedAttribute);

            element.Attributes.Append(visualizationAttribute);
            element.Attributes.Append(abbreviatedAttribute);

            element.AppendChild(hairpinPointElement);
            element.AppendChild(hairpinEnd1Element);
            element.AppendChild(hairpinEnd2Element);

            return element;
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when a property of the presented element has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void PresentedElement_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Expression")
                RaisePropertyChanged("Expression");
            else if (e.PropertyName == "ProgressType")
            {
                var presentedElement = (DynamicsProgressElement)PresentedElement;
                bool switchEnds = false;
                if (presentedElement.ProgressType == DynamicsProgressType.Crescendo)
                {
                    if (hairpinEnd1.X < hairpinPoint.X)
                        switchEnds = true;
                }
                else
                {
                    if (hairpinEnd1.X > hairpinPoint.X)
                        switchEnds = true;
                }

                if (switchEnds)
                {
                    var endX = hairpinEnd1.X;
                    HairpinEnd1 = new Point(hairpinPoint.X, hairpinEnd1.Y);
                    HairpinEnd2 = new Point(hairpinPoint.X, hairpinEnd2.Y);
                    HairpinPoint = new Point(endX, hairpinPoint.Y);
                }
                RaisePropertyChanged("ProgressType");
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the value indicating whether the textual representation of the mark
        /// should be abbreviated or not.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Boolean"/>
        /// </para>
        /// <para>
        /// Indicates whether the textual representation of the mark
        /// should be abbreviated (e.g. <i>cresc.</i> instead of <i>crescendo</i>) or not.<br/>
        /// Default value is <see langword="false"/>.
        /// </para>
        /// </value>
        public bool Abbreviated
        {
            get { return bool.Parse(abbreviatedAttribute.Value); }
            set
            {
                abbreviatedAttribute.Value = value.ToString();

                RaisePropertyChanged("Abbreviated");
            }
        }

        /// <summary>
        /// Gets or sets the textual expression that precises the progress type.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="string"/>
        /// </para>
        /// <para>
        /// A textual expression that precises the type of the dynamics progress.
        /// Examples are <i>molto</i> or <i>poco a poco</i>.<br/>
        /// Default value is an empty string ("").
        /// </para>
        /// </value>
        /// <seealso cref="DynamicsProgressElement.Expression"/>
        public string Expression
        {
            get { return ((DynamicsProgressElement)PresentedElement).Expression; }
            set { ((DynamicsProgressElement)PresentedElement).Expression = value; }
        }

        /// <summary>
        /// Gets or sets the position of the upper hairpin's end.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Point"/>
        /// </para>
        /// <para>
        /// The coordinates (in centimeters) of the hairpin's upper end
        /// relative to the top-left corner of the owner section.
        /// This property only has meaning if the <see cref="Visualization"/>
        /// is <see cref="DynamicsProgressDisplayMode.Hairpin"/>.
        /// </para>
        /// </value>
        public Point HairpinEnd1
        {
            get { return new Point(hairpinEnd1); }
            set
            {
                hairpinEnd1.SetOwner(null);
                hairpinEnd1 = value;
                hairpinEnd1.SetOwner(hairpinEnd1Element);

                RaisePropertyChanged("HairpinEnd1");
            }
        }

        /// <summary>
        /// Gets or sets the position of the lower hairpin's end.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Point"/>
        /// </para>
        /// <para>
        /// The coordinates (in centimeters) of the hairpin's lower end
        /// relative to the top-left corner of the owner section.
        /// This property only has meaning if the <see cref="Visualization"/>
        /// is <see cref="DynamicsProgressDisplayMode.Hairpin"/>.
        /// </para>
        /// </value>
        public Point HairpinEnd2
        {
            get { return new Point(hairpinEnd2); }
            set
            {
                hairpinEnd2.SetOwner(null);
                hairpinEnd2 = value;
                hairpinEnd2.SetOwner(hairpinEnd2Element);

                RaisePropertyChanged("HairpinEnd2");
            }
        }

        /// <summary>
        /// Gets or sets the position of the hairpin's point.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Point"/>
        /// </para>
        /// <para>
        /// The coordinates (in centimeters) of the hairpin's point
        /// relative to the top-left corner of the owner section.
        /// This property only has meaning if the <see cref="Visualization"/>
        /// is <see cref="DynamicsProgressDisplayMode.Hairpin"/>.
        /// </para>
        /// </value>
        public Point HairpinPoint
        {
            get { return new Point(hairpinPoint); }
            set
            {
                hairpinPoint.SetOwner(null);
                hairpinPoint = value;
                hairpinPoint.SetOwner(hairpinPointElement);

                RaisePropertyChanged("HairpinPoint");
            }
        }

        /// <summary>
        /// Gets or sets the type of the dynamics progress.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="DynamicsProgressType"/>
        /// </para>
        /// <para>
        /// The type of the dynamics progress.
        /// </para>
        /// </value>
        /// <seealso cref="DynamicsProgressElement.ProgressType"/>
        public DynamicsProgressType ProgressType
        {
            get { return ((DynamicsProgressElement)PresentedElement).ProgressType; }
            set { ((DynamicsProgressElement)PresentedElement).ProgressType = value; }
        }

        /// <summary>
        /// Gets or sets the visualization mode of this mark.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="DynamicsProgressDisplayMode"/>
        /// </para>
        /// <para>
        /// The visualization mode of this mark.<br/>
        /// Default value is <see cref="DynamicsProgressDisplayMode.Text"/>.
        /// </para>
        /// </value>
        public DynamicsProgressDisplayMode Visualization
        {
            get { return (DynamicsProgressDisplayMode)Enum.Parse(typeof(DynamicsProgressDisplayMode), visualizationAttribute.Value); }
            set
            {
                visualizationAttribute.Value = value.ToString();

                RaisePropertyChanged("Visualization");
            }
        }
        
        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="Abbreviated"/> property.
        /// </summary>
        private XmlAttribute abbreviatedAttribute;
        /// <summary>
        /// Holds the value of the <see cref="HairpinEnd1"/> property.
        /// </summary>
        private Point hairpinEnd1;
        /// <summary>
        /// Holds the data of the <see cref="HairpinEnd1"/> point.
        /// </summary>
        private XmlElement hairpinEnd1Element;
        /// <summary>
        /// Holds the value of the <see cref="HairpinEnd2"/> property.
        /// </summary>
        private Point hairpinEnd2;
        /// <summary>
        /// Holds the data of the <see cref="HairpinEnd2"/> point.
        /// </summary>
        private XmlElement hairpinEnd2Element;
        /// <summary>
        /// Holds the value of the <see cref="HairpinPoint"/> property.
        /// </summary>
        private Point hairpinPoint;
        /// <summary>
        /// Holds the data of the <see cref="HairpinPoint"/> point.
        /// </summary>
        private XmlElement hairpinPointElement;
        /// <summary>
        /// Holds the value of the <see cref="Visualization"/> property.
        /// </summary>
        private XmlAttribute visualizationAttribute;

        #endregion
    }

    /// <summary>
    /// Determines the type of visualization used for a <see cref="DynamicsProgressMark"/>.
    /// </summary>
    public enum DynamicsProgressDisplayMode
    {
        /// <summary>
        /// The mark is visualized as a hairpin (wedge).
        /// </summary>
        Hairpin,
        /// <summary>
        /// The mark is visualized using a text expression.
        /// </summary>
        Text
    }
}
